library(dplyr)
library(ggplot2)
library(tidytext)
library(rtweet)
library(plotly)
library(circlize) # https://jokergoo.github.io/circlize_book/book/

load("data/transcripts.RData")
load("data/ratings.RData")

Proposta inicial

Inicialmente, minha ideia era utilizar dados do twitter (texto e hora do tweet) de alguma partida de futebol e comparar a frequência de determinadas palavras com os eventos do jogo.

No entanto, um problema que surgiu foi que a api gratuita do Twitter aparentemente não retorna tweets de maneira constante ao decorrer da partida. Segue um exemplo em que temos tweets apenas para alguns intervalos de tempo do jogo.

tweets = parse_stream("CEAxFLU.json")
ts_plot(tweets, by = "mins") + # função do rtweet que utiliza o ggplot
  theme_bw()

Este problema me fez desistir da proposta inicial e buscar outras opções para o trabalho final.

Nova ideia

Alguns meses atrás, navegando no Reddit, encontrei uma visualização que me chamou bastante atenção:

na época, eu estava estudando mineração de textos e tive a ideia de utilizar métodos de text mining para buscar palavras que identifiquem cada personagem e avaliar a relação entre a quantidade de falas de um personagem em um episódio com a nota do episódio no IMDB. Como eu nunca assisti The Office, a série desta visualização, decidi utilizar os dados da série Avatar: The Last Airbender (ATLA). Grande parte do trabalho de obtenção e limpeza dos dados assim como a parte de mineração de textos já estava pronta, porém eu não havia feito praticamente nenhuma visualização para este projeto. Desse modo, optei por continuar este projeto fazendo as visualizações neste trabalho final.

Datasets

Os datasets que serão utilizados são:

Ambos os conjuntos de dados foram coletados via web scraping. O código do scrape está no arquivo scrape.R.

Data frame do roteiro

transcripts

Data frame das ratings

ratings

Principais palavras por personagem

Inicialmente, realizei um pre-processamento nos textos, removi as introduções e tudo que estava entre colchetes.

Exemplo de texto antes da limpeza

transcripts$text[2]
[1] "It's not getting away from me this time. [Close-up of the boy as he grins confidently over his shoulder in the direction of the girl.] Watch and learn, Katara. This is how you catch a fish.\n"
intro = transcripts$text[1]

transcripts = transcripts %>%
  filter(text != intro) %>% # tirando as introducoes
  mutate(text = stringr::str_replace_all(text, '\\[(.*?)\\]', ''), # tirando o que ta entre colchetes
         text = qdapRegex::rm_number(text))

Exemplo de texto depois da limpeza

transcripts$text[1] # índice 1 pois removi a introdução
[1] "It's not getting away from me this time. Watch and learn, Katara. This is how you catch a fish."

Agora, colocarei o texto no formato de um data frame de uma palavra por linha, com todas as palavras em caixa baixa e removerei as seguintes stop words:

sort(unique(stop_words$word))
  [1] "a"             "a's"           "able"          "about"         "above"        
  [6] "according"     "accordingly"   "across"        "actually"      "after"        
 [11] "afterwards"    "again"         "against"       "ain't"         "all"          
 [16] "allow"         "allows"        "almost"        "alone"         "along"        
 [21] "already"       "also"          "although"      "always"        "am"           
 [26] "among"         "amongst"       "an"            "and"           "another"      
 [31] "any"           "anybody"       "anyhow"        "anyone"        "anything"     
 [36] "anyway"        "anyways"       "anywhere"      "apart"         "appear"       
 [41] "appreciate"    "appropriate"   "are"           "area"          "areas"        
 [46] "aren't"        "around"        "as"            "aside"         "ask"          
 [51] "asked"         "asking"        "asks"          "associated"    "at"           
 [56] "available"     "away"          "awfully"       "b"             "back"         
 [61] "backed"        "backing"       "backs"         "be"            "became"       
 [66] "because"       "become"        "becomes"       "becoming"      "been"         
 [71] "before"        "beforehand"    "began"         "behind"        "being"        
 [76] "beings"        "believe"       "below"         "beside"        "besides"      
 [81] "best"          "better"        "between"       "beyond"        "big"          
 [86] "both"          "brief"         "but"           "by"            "c"            
 [91] "c'mon"         "c's"           "came"          "can"           "can't"        
 [96] "cannot"        "cant"          "case"          "cases"         "cause"        
[101] "causes"        "certain"       "certainly"     "changes"       "clear"        
[106] "clearly"       "co"            "com"           "come"          "comes"        
[111] "concerning"    "consequently"  "consider"      "considering"   "contain"      
[116] "containing"    "contains"      "corresponding" "could"         "couldn't"     
[121] "course"        "currently"     "d"             "definitely"    "described"    
[126] "despite"       "did"           "didn't"        "differ"        "different"    
[131] "differently"   "do"            "does"          "doesn't"       "doing"        
[136] "don't"         "done"          "down"          "downed"        "downing"      
[141] "downs"         "downwards"     "during"        "e"             "each"         
[146] "early"         "edu"           "eg"            "eight"         "either"       
[151] "else"          "elsewhere"     "end"           "ended"         "ending"       
[156] "ends"          "enough"        "entirely"      "especially"    "et"           
[161] "etc"           "even"          "evenly"        "ever"          "every"        
[166] "everybody"     "everyone"      "everything"    "everywhere"    "ex"           
[171] "exactly"       "example"       "except"        "f"             "face"         
[176] "faces"         "fact"          "facts"         "far"           "felt"         
[181] "few"           "fifth"         "find"          "finds"         "first"        
[186] "five"          "followed"      "following"     "follows"       "for"          
[191] "former"        "formerly"      "forth"         "four"          "from"         
[196] "full"          "fully"         "further"       "furthered"     "furthering"   
[201] "furthermore"   "furthers"      "g"             "gave"          "general"      
[206] "generally"     "get"           "gets"          "getting"       "give"         
[211] "given"         "gives"         "go"            "goes"          "going"        
[216] "gone"          "good"          "goods"         "got"           "gotten"       
[221] "great"         "greater"       "greatest"      "greetings"     "group"        
[226] "grouped"       "grouping"      "groups"        "h"             "had"          
[231] "hadn't"        "happens"       "hardly"        "has"           "hasn't"       
[236] "have"          "haven't"       "having"        "he"            "he'd"         
[241] "he'll"         "he's"          "hello"         "help"          "hence"        
[246] "her"           "here"          "here's"        "hereafter"     "hereby"       
[251] "herein"        "hereupon"      "hers"          "herself"       "hi"           
[256] "high"          "higher"        "highest"       "him"           "himself"      
[261] "his"           "hither"        "hopefully"     "how"           "how's"        
[266] "howbeit"       "however"       "i"             "i'd"           "i'll"         
[271] "i'm"           "i've"          "ie"            "if"            "ignored"      
[276] "immediate"     "important"     "in"            "inasmuch"      "inc"          
[281] "indeed"        "indicate"      "indicated"     "indicates"     "inner"        
[286] "insofar"       "instead"       "interest"      "interested"    "interesting"  
[291] "interests"     "into"          "inward"        "is"            "isn't"        
[296] "it"            "it'd"          "it'll"         "it's"          "its"          
[301] "itself"        "j"             "just"          "k"             "keep"         
[306] "keeps"         "kept"          "kind"          "knew"          "know"         
[311] "known"         "knows"         "l"             "large"         "largely"      
[316] "last"          "lately"        "later"         "latest"        "latter"       
[321] "latterly"      "least"         "less"          "lest"          "let"          
[326] "let's"         "lets"          "like"          "liked"         "likely"       
[331] "little"        "long"          "longer"        "longest"       "look"         
[336] "looking"       "looks"         "ltd"           "m"             "made"         
[341] "mainly"        "make"          "making"        "man"           "many"         
[346] "may"           "maybe"         "me"            "mean"          "meanwhile"    
[351] "member"        "members"       "men"           "merely"        "might"        
[356] "more"          "moreover"      "most"          "mostly"        "mr"           
[361] "mrs"           "much"          "must"          "mustn't"       "my"           
[366] "myself"        "n"             "name"          "namely"        "nd"           
[371] "near"          "nearly"        "necessary"     "need"          "needed"       
[376] "needing"       "needs"         "neither"       "never"         "nevertheless" 
[381] "new"           "newer"         "newest"        "next"          "nine"         
[386] "no"            "nobody"        "non"           "none"          "noone"        
[391] "nor"           "normally"      "not"           "nothing"       "novel"        
[396] "now"           "nowhere"       "number"        "numbers"       "o"            
[401] "obviously"     "of"            "off"           "often"         "oh"           
[406] "ok"            "okay"          "old"           "older"         "oldest"       
[411] "on"            "once"          "one"           "ones"          "only"         
[416] "onto"          "open"          "opened"        "opening"       "opens"        
[421] "or"            "order"         "ordered"       "ordering"      "orders"       
[426] "other"         "others"        "otherwise"     "ought"         "our"          
[431] "ours"          "ourselves"     "out"           "outside"       "over"         
[436] "overall"       "own"           "p"             "part"          "parted"       
[441] "particular"    "particularly"  "parting"       "parts"         "per"          
[446] "perhaps"       "place"         "placed"        "places"        "please"       
[451] "plus"          "point"         "pointed"       "pointing"      "points"       
[456] "possible"      "present"       "presented"     "presenting"    "presents"     
[461] "presumably"    "probably"      "problem"       "problems"      "provides"     
[466] "put"           "puts"          "q"             "que"           "quite"        
[471] "qv"            "r"             "rather"        "rd"            "re"           
[476] "really"        "reasonably"    "regarding"     "regardless"    "regards"      
[481] "relatively"    "respectively"  "right"         "room"          "rooms"        
[486] "s"             "said"          "same"          "saw"           "say"          
[491] "saying"        "says"          "second"        "secondly"      "seconds"      
[496] "see"           "seeing"        "seem"          "seemed"        "seeming"      
[501] "seems"         "seen"          "sees"          "self"          "selves"       
[506] "sensible"      "sent"          "serious"       "seriously"     "seven"        
[511] "several"       "shall"         "shan't"        "she"           "she'd"        
[516] "she'll"        "she's"         "should"        "shouldn't"     "show"         
[521] "showed"        "showing"       "shows"         "side"          "sides"        
[526] "since"         "six"           "small"         "smaller"       "smallest"     
[531] "so"            "some"          "somebody"      "somehow"       "someone"      
[536] "something"     "sometime"      "sometimes"     "somewhat"      "somewhere"    
[541] "soon"          "sorry"         "specified"     "specify"       "specifying"   
[546] "state"         "states"        "still"         "sub"           "such"         
[551] "sup"           "sure"          "t"             "t's"           "take"         
[556] "taken"         "tell"          "tends"         "th"            "than"         
[561] "thank"         "thanks"        "thanx"         "that"          "that's"       
[566] "thats"         "the"           "their"         "theirs"        "them"         
[571] "themselves"    "then"          "thence"        "there"         "there's"      
[576] "thereafter"    "thereby"       "therefore"     "therein"       "theres"       
[581] "thereupon"     "these"         "they"          "they'd"        "they'll"      
[586] "they're"       "they've"       "thing"         "things"        "think"        
[591] "thinks"        "third"         "this"          "thorough"      "thoroughly"   
[596] "those"         "though"        "thought"       "thoughts"      "three"        
[601] "through"       "throughout"    "thru"          "thus"          "to"           
[606] "today"         "together"      "too"           "took"          "toward"       
[611] "towards"       "tried"         "tries"         "truly"         "try"          
[616] "trying"        "turn"          "turned"        "turning"       "turns"        
[621] "twice"         "two"           "u"             "un"            "under"        
[626] "unfortunately" "unless"        "unlikely"      "until"         "unto"         
[631] "up"            "upon"          "us"            "use"           "used"         
[636] "useful"        "uses"          "using"         "usually"       "uucp"         
[641] "v"             "value"         "various"       "very"          "via"          
[646] "viz"           "vs"            "w"             "want"          "wanted"       
[651] "wanting"       "wants"         "was"           "wasn't"        "way"          
[656] "ways"          "we"            "we'd"          "we'll"         "we're"        
[661] "we've"         "welcome"       "well"          "wells"         "went"         
[666] "were"          "weren't"       "what"          "what's"        "whatever"     
[671] "when"          "when's"        "whence"        "whenever"      "where"        
[676] "where's"       "whereafter"    "whereas"       "whereby"       "wherein"      
[681] "whereupon"     "wherever"      "whether"       "which"         "while"        
[686] "whither"       "who"           "who's"         "whoever"       "whole"        
[691] "whom"          "whose"         "why"           "why's"         "will"         
[696] "willing"       "wish"          "with"          "within"        "without"      
[701] "won't"         "wonder"        "work"          "worked"        "working"      
[706] "works"         "would"         "wouldn't"      "x"             "y"            
[711] "year"          "years"         "yes"           "yet"           "you"          
[716] "you'd"         "you'll"        "you're"        "you've"        "young"        
[721] "younger"       "youngest"      "your"          "yours"         "yourself"     
[726] "yourselves"    "z"             "zero"         
ut = transcripts %>%
  unnest_tokens(word, text) %>% 
  anti_join(stop_words, by = "word") # retirando stop words
ut

Contagem de palavras por personagem (após remoção de stop words):

count_speaker = ut %>%
  count(speaker) %>%
  arrange(desc(n))
count_speaker

Como nesta série, cada personagem tem uma origin ligada a um dos quatro elementos (água, fogo, terra e ar), optei por inserir esta informação nas visualizações.

As cores selecionadas foram escolhidas a partir da vestimenta dos personagens e da seguinte imagem:

water = "#27A3EB"
fire = "#D7333C"
earth = "#6FCC36"
air = "gold1"
personagens = count_speaker$speaker[1:18]
origin = tibble(speaker = personagens, origin = c("water", "air", "water", "fire", "fire", "earth", "fire", "fire", "earth", "water", "air", "fire", "fire", "earth", "water", "earth", "earth", "fire"))
origin
count_speaker$Selecionado = "Não"
count_speaker$Selecionado[1:18] = "Sim"
n_outros = sum(count_speaker$n[which(count_speaker$Selecionado == "Não")])
count_speaker %>%
  head(24) %>%
  rbind(tibble(speaker = "Outros", n = n_outros, Selecionado = "Não")) %>%
  mutate(speaker = factor(speaker, levels = speaker)) %>%
  ggplot(aes(x = speaker, y = n, alpha = Selecionado, fill = speaker)) +
  geom_bar(stat = "identity") +
  xlab("Personagem") +
  ylab("Quantidade de palavras") +
  theme_bw() +
  theme(axis.text.x = element_text(angle = 90, vjust = 0.3)) +
  scale_alpha_discrete(range = c(0.3, 0.9)) +
  ggtitle("Palavras por personagens e personagens selecionados") +
  guides(alpha = guide_legend(reverse = TRUE), fill = FALSE) +
  scale_fill_manual(values = c(water, air, water, fire, fire, earth,
                               fire, fire, earth, water, air, fire,
                               fire, earth, water, earth, earth, fire,
                               earth, water, fire, fire, fire, water, "black"))

Optei por utilizar apenas os 18 personagens com maior número de palavras e calcular as palavras mais utilizadas e a medida tf-idf considerando cada personagem como um documento. A medida tf-idf nos permite avaliar palavras que caracterizam um determinado documento, neste caso personagem, em relação a um conjunto de documentos. Ela é calculada da seguinte forma:

\[ w_{t,d} = \frac{tf_{t,d}}{\sum_{t' \in d}f_{t',d}} \times log \Bigg( \frac{N}{n_t} \Bigg) \]

em que

tfidf = ut %>%
  filter(speaker %in% personagens) %>%
  count(word, speaker) %>%
  bind_tf_idf(word, speaker, n) %>%
  arrange(desc(tf_idf))

Visualizando as palavras mais utilizadas e os maiores tf-idf por personagem

tmp1 = tibble(speaker = count_speaker$speaker[1:18], 
                   order_speaker = 1:18)

tmp2 = tfidf %>%
  inner_join(tmp1, by = "speaker") %>%
  mutate(speaker = reorder(speaker, order_speaker)) %>%
  arrange(speaker, desc(n)) %>%
  group_by(speaker) %>%
  do(head(., 10)) %>%
  ungroup() %>%
  arrange(speaker, n) %>%
  mutate(order_word = row_number())

tmp3 = tfidf %>%
  inner_join(tmp1, by = "speaker") %>%
  mutate(speaker = reorder(speaker, order_speaker)) %>%
  arrange(speaker, desc(tf_idf)) %>%
  group_by(speaker) %>%
  do(head(., 10)) %>%
  ungroup() %>%
  arrange(speaker, tf_idf) %>%
  mutate(order_word = row_number())
tmp2 %>%
  ggplot(aes(x = order_word, y = n, fill = speaker)) +
  geom_bar(stat = "identity", show.legend = FALSE) +
  facet_wrap(~ speaker, scales = "free", ncol = 6) +
  theme_bw() +
  scale_x_continuous(breaks = tmp2$order_word, labels = tmp2$word) +
  scale_y_continuous(breaks = scales::pretty_breaks(2)) +
  coord_flip() +
  labs(title = "Palavras mais utilizadas por personagens",
       x = NULL,
       y = "n") +
  scale_fill_manual(values = c(water, air, water, fire, fire, earth,
                               fire, fire, earth, water, air, fire,
                               fire, earth, water, earth, earth, fire))

tmp3 %>%
  ggplot(aes(x = order_word, y = tf_idf, fill = speaker)) +
  geom_bar(stat = "identity", show.legend = FALSE) +
  facet_wrap(~ speaker, scales = "free", ncol = 6) +
  xlab("words") +
  ylab("tf-idf") +
  theme_bw() +
  scale_x_continuous(breaks = tmp3$order_word, labels = tmp3$word) +
  scale_y_continuous(breaks = scales::pretty_breaks(2)) +
  coord_flip() +
  labs(title = "Palavras com maiores tf-idf por personagem",
       x = NULL,
       y = "tf-idf") +
  scale_fill_manual(values = c(water, air, water, fire, fire, earth,
                               fire, fire, earth, water, air, fire,
                               fire, earth, water, earth, earth, fire))

Finalmente, fiz uma visualização da matriz de frequência dos termos destes personagens utilizando análise de agrupamentos hierárquica para fazer a ordenação das linhas e colunas desta matriz.

mat = reshape2::acast(tfidf, word ~ speaker, value.var = "tf", fill = 0) 
rownames(mat) = rep("", nrow(mat)) 
heatmap(mat)

O que chama a atenção nesta matriz é que existem diversos termos que são utilizados apenas por um único personagem.

Exemplo de termos que apenas o personagem “Sokka” disse:

sokka = ut %>%
  filter(speaker == "Sokka") %>%
  count(word)

not_sokka = ut %>%
  filter(speaker != "Sokka") %>%
  select(word) %>%
  distinct(.keep_all = TRUE)

sokka %>%
  anti_join(not_sokka, by = "word") %>%
  arrange(desc(n))

Relação entre a quantidade de palavras dos personagens e notas do IMDB

Primeiramente, agrupei as informações das notas com a base do roteiro e criei uma variável para indicar a quantidade de palavras dos personagens por episódio.

tmp = tibble(epi_num = unique(transcripts$epi_num), rating = ratings$rating)

words_epi = ut %>%
  inner_join(tmp, by = "epi_num") %>%
  filter(speaker %in% personagens) %>%
  count(epi_num, rating, speaker)

words_epi

Por exemplo o personagem Aang disse 125 palavras (com remoção de stop words) no episódio 1.

Agora, visualizamos a base com as notas dos episódios e a quantidade de falas dos personagens por episódio.

ratings$Temporada = as.factor(c(rep("Book One: Air", 20), rep("Book two: Earth", 20), rep("Book Three: Fire", 21)))

p = ratings %>%
  mutate(text = paste0("Episódio: ", epi_num, "<br>",
                       "Título ", epi_name, "<br>",
                       "Nota: ", rating, "<br>")) %>%
  ggplot(aes(x = epi_num, y = rating, fill = Temporada, text = text)) +
  geom_bar(stat = "identity") +
  scale_fill_manual(values = c(water, earth, fire)) +
  theme_bw() +
  coord_cartesian(ylim = c(0, 10)) +
  ggtitle("Nota do IMDB por episódio") +
  xlab("Episódio") +
  ylab("Nota") +
  scale_x_continuous(expand = c(0, 0)) +
  scale_y_continuous(expand = c(0, 0))
  
p %>% ggplotly(tooltip = c("text"))
tmp4 = reshape2::acast(words_epi, epi_num ~ speaker, value.var = "n", fill = 0) %>%
  reshape2::melt()
names(tmp4) = c("epi_num", "speaker", "n")
tmp4 = tmp4 %>%
  inner_join(tmp1, by = "speaker") %>%
  mutate(speaker = reorder(speaker, order_speaker)) 
tmp4 %>%
  ggplot(aes(x = epi_num, y = n, fill = speaker)) +
  geom_bar(stat = "identity", show.legend = FALSE) +
  facet_wrap(~ speaker, scales = "free", ncol = 3) +
  ylim(0, 300) +
  theme_bw() +
  scale_fill_manual(values = c(water, air, water, fire, fire, earth,
                               fire, fire, earth, water, air, fire,
                               fire, earth, water, earth, earth, fire)) +
  ggtitle("Quantidade de palavras por episódio") +
  xlab("Episódio") +
  ylab("Quantidade de palavras") +
  scale_x_continuous(expand = c(0, 0))

Posteriormente, calculei a correlação de Spearman entre a proporção de falas do personagem e a nota do IMDB. Preferi utilizar esta medida de correlação ao invés da correlação de Pearson pois não espero que a correlação entre estas variáveis seja linear visto que existem muitos personagens que não possuem falas em diversos episódios.

f <- function(personagem) {
  out = NULL
  for(i in 1:61) {
    tmp = words_epi %>%
      filter(speaker == personagem,
             epi_num == i) %>%
      .$n
    out[i] = ifelse(length(tmp) == 0, 0, tmp)
  }
  out
}

mat2 = ratings$rating %>%
  cbind(sapply(personagens, f))
colnames(mat2)[1] = "rating"

mat_rating = cor(mat2, method = "spearman")

cor_rating = mat_rating[-1, 1]

sort(cor_rating, decreasing = TRUE)
       Ozai        Toph       Azula        Zuko      Hakoda        Suki        Iroh 
 0.40201919  0.33825857  0.30884073  0.29001220  0.23496605  0.20880807  0.18218638 
       Roku        Zhao        Bumi   Long Feng        Hama         Mai      Pathik 
 0.17188827  0.16380385  0.15973407  0.14013893  0.12121646  0.11751625  0.05556310 
        Jet       Sokka        Aang      Katara 
-0.06679389 -0.43891654 -0.46230858 -0.55510008 
tibble(spearman = cor_rating, speaker = names(cor_rating)) %>%
  inner_join(origin, by = "speaker") %>%
  mutate(speaker = reorder(speaker, spearman)) %>%
  ggplot(aes(speaker, spearman, fill = origin)) +
  geom_bar(stat = "identity") +
  xlab("Personagem") +
  ylab("Correlação de Spearman") +
  ggtitle("Correlações de Spearman \nentre a quantidade de palavras do personagem por episódio e notas do IMDB") +
  theme_bw() +
  scale_y_continuous(breaks = scales::pretty_breaks(3)) +
  scale_fill_manual(values = c("water" = water, "fire" = fire, "air" = air, "earth" = earth)) +
  guides(fill = FALSE) +
  theme(axis.text.x = element_text(angle = 90, vjust = 0.3))

Relações entre os personagens

Utilizando a matriz de correlação criada na seção anterior, realizei uma análise de agrupamentos hierárquica para buscar personagens que tendem a ter quantidades de falas similares nos mesmos episódios.

clusters = hclust(as.dist(1-mat_rating[-1,-1]))
ggdendro::ggdendrogram(clusters)

Podemos ver que as maiores similaridades estão entre Azula/Mai e Sokka/Katara. O que faz sentido visto que estes pares tendem a aparecer juntos nos episódios.

Posteriormente, considerei apenas as palavras referentes aos nomes dos 18 personagens selecionados e ilustrei a quantidade de vezes que um personagem diz o nome de outro. Note que a diferença das alturas indicam a direção das falas.

tmp6 = tfidf %>%
  filter(word %in% stringr::str_to_lower(c(personagens, "feng"))) %>%
  mutate(speaker = stringr::str_to_lower(speaker))
tmp6$speaker[which(tmp6$speaker == "long feng")] = "feng"

mat3 = reshape2::acast(tmp6, speaker ~ word, value.var = "n", fill = 0) 

colnames(mat3) = c("Aang", "Azula", "Bumi", "Feng", "Hakoda", "Hama", "Iroh",
                  "Jet", "Katara", "Mai", "Ozai", "Pathik", "Roku", "Sokka",
                  "Suki", "Toph", "Zhao", "Zuko")
rownames(mat3) = colnames(mat3)
grid.col = c(Aang = air, Azula = fire, Bumi = earth, Feng = earth, Hakoda = water,
             Hama = water, Iroh = fire, Jet = earth, Katara = water, Mai = fire,
             Ozai = fire, Pathik = air, Roku = fire, Sokka = water, Suki = earth,
             Toph = earth, Zhao = fire, Zuko = fire)

circos.clear()
circos.par(start.degree = 90, clock.wise = TRUE)
chordDiagram(mat3, grid.col = grid.col, annotationTrack = c("name", "grid"), directional = 1, diffHeight = uh(7), order = c("Pathik", "Aang", "Katara", "Sokka", "Hakoda", "Hama", "Toph", "Bumi", "Feng", "Jet", "Suki", "Zuko", "Roku", "Ozai", "Iroh", "Azula", "Mai", "Zhao"))

Referências

LS0tDQp0aXRsZTogIlRyYWJhbGhvIEZpbmFsIC0gVmlzdWFsaXphw6fDo28gZGEgSW5mb3JtYcOnw6NvIg0KYXV0aG9yOiAiRnVuZGHDp8OjbyBHZXR1bGlvIFZhcmdhcyA8YnI+IE1lc3RyYWRvIGVtIE1vZGVsYWdlbSBNYXRlbcOhdGljYSA8YnI+IFByb2YuIEFzbGEgTWVkZWlyb3MgZSBTw6EgPGJyPiBBbHVubzogTHVpeiBGZXJuYW5kbyBNYWlhIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KYGBge3IsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkodGlkeXRleHQpDQpsaWJyYXJ5KHJ0d2VldCkNCmxpYnJhcnkocGxvdGx5KQ0KbGlicmFyeShjaXJjbGl6ZSkgIyBodHRwczovL2pva2VyZ29vLmdpdGh1Yi5pby9jaXJjbGl6ZV9ib29rL2Jvb2svDQoNCmxvYWQoImRhdGEvdHJhbnNjcmlwdHMuUkRhdGEiKQ0KbG9hZCgiZGF0YS9yYXRpbmdzLlJEYXRhIikNCmBgYA0KDQojIyBQcm9wb3N0YSBpbmljaWFsDQoNCkluaWNpYWxtZW50ZSwgbWluaGEgaWRlaWEgZXJhIHV0aWxpemFyIGRhZG9zIGRvIHR3aXR0ZXIgKHRleHRvIGUgaG9yYSBkbyB0d2VldCkgZGUgYWxndW1hIHBhcnRpZGEgZGUgZnV0ZWJvbCBlIGNvbXBhcmFyIGEgZnJlcXXDqm5jaWEgZGUgZGV0ZXJtaW5hZGFzIHBhbGF2cmFzIGNvbSBvcyBldmVudG9zIGRvIGpvZ28uIA0KDQpObyBlbnRhbnRvLCB1bSBwcm9ibGVtYSBxdWUgc3VyZ2l1IGZvaSBxdWUgYSBhcGkgZ3JhdHVpdGEgZG8gVHdpdHRlciBhcGFyZW50ZW1lbnRlIG7Do28gcmV0b3JuYSB0d2VldHMgZGUgbWFuZWlyYSBjb25zdGFudGUgYW8gZGVjb3JyZXIgZGEgcGFydGlkYS4gU2VndWUgdW0gZXhlbXBsbyBlbSBxdWUgdGVtb3MgdHdlZXRzIGFwZW5hcyBwYXJhIGFsZ3VucyBpbnRlcnZhbG9zIGRlIHRlbXBvIGRvIGpvZ28uDQpgYGB7ciwgZHBpID0gMzAwfQ0KdHdlZXRzID0gcGFyc2Vfc3RyZWFtKCJDRUF4RkxVLmpzb24iKQ0KdHNfcGxvdCh0d2VldHMsIGJ5ID0gIm1pbnMiKSArICMgZnVuw6fDo28gZG8gcnR3ZWV0IHF1ZSB1dGlsaXphIG8gZ2dwbG90DQogIHRoZW1lX2J3KCkNCmBgYA0KDQpFc3RlIHByb2JsZW1hIG1lIGZleiBkZXNpc3RpciBkYSBwcm9wb3N0YSBpbmljaWFsIGUgYnVzY2FyIG91dHJhcyBvcMOnw7VlcyBwYXJhIG8gdHJhYmFsaG8gZmluYWwuDQoNCiMjIE5vdmEgaWRlaWENCg0KQWxndW5zIG1lc2VzIGF0csOhcywgbmF2ZWdhbmRvIG5vIFJlZGRpdCwgZW5jb250cmVpIHVtYSB2aXN1YWxpemHDp8OjbyBxdWUgbWUgY2hhbW91IGJhc3RhbnRlIGF0ZW7Dp8OjbzoNCg0KIVs8YSBocmVmPSJodHRwczovL3d3dy5yZWRkaXQuY29tL3IvZGF0YWlzYmVhdXRpZnVsL2NvbW1lbnRzLzhhNGdici90aGVfb2ZmaWNlX2NoYXJhY3RlcnNfbW9zdF9kaXN0aW5ndWlzaGluZ193b3Jkc19vYy8/c29ydD1jb25maWRlbmNlIj5MaW5rIGRhIHZpc3VhbGl6YcOnw6NvPC9hPl0oZGF0YS90aGVfb2ZmaWNlLnBuZykNCg0KbmEgw6lwb2NhLCBldSBlc3RhdmEgZXN0dWRhbmRvIG1pbmVyYcOnw6NvIGRlIHRleHRvcyBlIHRpdmUgYSBpZGVpYSBkZSB1dGlsaXphciBtw6l0b2RvcyBkZSB0ZXh0IG1pbmluZyBwYXJhIGJ1c2NhciBwYWxhdnJhcyBxdWUgaWRlbnRpZmlxdWVtIGNhZGEgcGVyc29uYWdlbSBlIGF2YWxpYXIgYSByZWxhw6fDo28gZW50cmUgYSBxdWFudGlkYWRlIGRlIGZhbGFzIGRlIHVtIHBlcnNvbmFnZW0gZW0gdW0gZXBpc8OzZGlvIGNvbSBhIG5vdGEgZG8gZXBpc8OzZGlvIG5vIElNREIuIENvbW8gZXUgbnVuY2EgYXNzaXN0aSBUaGUgT2ZmaWNlLCBhIHPDqXJpZSBkZXN0YSB2aXN1YWxpemHDp8OjbywgZGVjaWRpIHV0aWxpemFyIG9zIGRhZG9zIGRhIHPDqXJpZSANCjxhIGhyZWY9Imh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0F2YXRhcjpfVGhlX0xhc3RfQWlyYmVuZGVyIj5BdmF0YXI6IFRoZSBMYXN0IEFpcmJlbmRlciAoQVRMQSk8L2E+LiBHcmFuZGUgcGFydGUgZG8gdHJhYmFsaG8gZGUgb2J0ZW7Dp8OjbyBlIGxpbXBlemEgZG9zIGRhZG9zIGFzc2ltIGNvbW8gYSBwYXJ0ZSBkZSBtaW5lcmHDp8OjbyBkZSB0ZXh0b3MgasOhIGVzdGF2YSBwcm9udGEsIHBvcsOpbSBldSBuw6NvIGhhdmlhIGZlaXRvIHByYXRpY2FtZW50ZSBuZW5odW1hIHZpc3VhbGl6YcOnw6NvIHBhcmEgZXN0ZSBwcm9qZXRvLiBEZXNzZSBtb2RvLCBvcHRlaSBwb3IgY29udGludWFyIGVzdGUgcHJvamV0byBmYXplbmRvIGFzIHZpc3VhbGl6YcOnw7VlcyBuZXN0ZSB0cmFiYWxobyBmaW5hbC4NCg0KDQojIyBEYXRhc2V0cw0KDQpPcyBkYXRhc2V0cyBxdWUgc2Vyw6NvIHV0aWxpemFkb3Mgc8OjbzoNCg0KKyBPIHNjcmlwdCBkb3MgNjEgZXBpc8OzZGlvcyBkbyBBVExBIGRpc3BvbsOtdmVsIGVtOiBodHRwOi8vYXZhdGFyLndpa2lhLmNvbS93aWtpL0F2YXRhcl9XaWtpOlRyYW5zY3JpcHRzOw0KDQorIEFzIG5vdGFzIGRvIElNREIgZGVzdGEgc8OpcmllIHF1ZSBwb2RlIHNlciBlbmNvbnRyYWRhIGVtOiBodHRwczovL3d3dy5pbWRiLmNvbS90aXRsZS90dDA0MTcyOTkvLg0KDQpBbWJvcyBvcyBjb25qdW50b3MgZGUgZGFkb3MgZm9yYW0gY29sZXRhZG9zIHZpYSB3ZWIgc2NyYXBpbmcuIE8gY8OzZGlnbyBkbyBzY3JhcGUgZXN0w6Egbm8gYXJxdWl2byA8YSBocmVmPSJzY3JhcGUuUiI+c2NyYXBlLlI8L2E+Lg0KDQpEYXRhIGZyYW1lIGRvIHJvdGVpcm8NCmBgYHtyfQ0KdHJhbnNjcmlwdHMNCmBgYA0KDQpEYXRhIGZyYW1lIGRhcyByYXRpbmdzDQpgYGB7cn0NCnJhdGluZ3MNCmBgYA0KDQojIyBQcmluY2lwYWlzIHBhbGF2cmFzIHBvciBwZXJzb25hZ2VtDQoNCkluaWNpYWxtZW50ZSwgcmVhbGl6ZWkgdW0gcHJlLXByb2Nlc3NhbWVudG8gbm9zIHRleHRvcywgcmVtb3ZpIGFzIGludHJvZHXDp8O1ZXMgZSB0dWRvIHF1ZSBlc3RhdmEgZW50cmUgY29sY2hldGVzLg0KDQpFeGVtcGxvIGRlIHRleHRvIGFudGVzIGRhIGxpbXBlemENCmBgYHtyfQ0KdHJhbnNjcmlwdHMkdGV4dFsyXQ0KYGBgDQoNCmBgYHtyfQ0KaW50cm8gPSB0cmFuc2NyaXB0cyR0ZXh0WzFdDQoNCnRyYW5zY3JpcHRzID0gdHJhbnNjcmlwdHMgJT4lDQogIGZpbHRlcih0ZXh0ICE9IGludHJvKSAlPiUgIyB0aXJhbmRvIGFzIGludHJvZHVjb2VzDQogIG11dGF0ZSh0ZXh0ID0gc3RyaW5ncjo6c3RyX3JlcGxhY2VfYWxsKHRleHQsICdcXFsoLio/KVxcXScsICcnKSwgIyB0aXJhbmRvIG8gcXVlIHRhIGVudHJlIGNvbGNoZXRlcw0KICAgICAgICAgdGV4dCA9IHFkYXBSZWdleDo6cm1fbnVtYmVyKHRleHQpKQ0KYGBgDQoNCkV4ZW1wbG8gZGUgdGV4dG8gZGVwb2lzIGRhIGxpbXBlemENCmBgYHtyfQ0KdHJhbnNjcmlwdHMkdGV4dFsxXSAjIMOtbmRpY2UgMSBwb2lzIHJlbW92aSBhIGludHJvZHXDp8Ojbw0KYGBgDQoNCkFnb3JhLCBjb2xvY2FyZWkgbyB0ZXh0byBubyBmb3JtYXRvIGRlIHVtIGRhdGEgZnJhbWUgZGUgdW1hIHBhbGF2cmEgcG9yIGxpbmhhLCBjb20gdG9kYXMgYXMgcGFsYXZyYXMgZW0gY2FpeGEgYmFpeGEgZSByZW1vdmVyZWkgYXMgc2VndWludGVzIHN0b3Agd29yZHM6DQpgYGB7cn0NCnNvcnQodW5pcXVlKHN0b3Bfd29yZHMkd29yZCkpDQpgYGANCg0KYGBge3J9DQp1dCA9IHRyYW5zY3JpcHRzICU+JQ0KICB1bm5lc3RfdG9rZW5zKHdvcmQsIHRleHQpICU+JSANCiAgYW50aV9qb2luKHN0b3Bfd29yZHMsIGJ5ID0gIndvcmQiKSAjIHJldGlyYW5kbyBzdG9wIHdvcmRzDQpgYGANCg0KYGBge3J9DQp1dA0KYGBgDQoNCkNvbnRhZ2VtIGRlIHBhbGF2cmFzIHBvciBwZXJzb25hZ2VtIChhcMOzcyByZW1vw6fDo28gZGUgc3RvcCB3b3Jkcyk6DQpgYGB7cn0NCmNvdW50X3NwZWFrZXIgPSB1dCAlPiUNCiAgY291bnQoc3BlYWtlcikgJT4lDQogIGFycmFuZ2UoZGVzYyhuKSkNCmNvdW50X3NwZWFrZXINCmBgYA0KDQpDb21vIG5lc3RhIHPDqXJpZSwgY2FkYSBwZXJzb25hZ2VtIHRlbSB1bWEgb3JpZ2luIGxpZ2FkYSBhIHVtIGRvcyBxdWF0cm8gZWxlbWVudG9zICjDoWd1YSwgZm9nbywgdGVycmEgZSBhciksIG9wdGVpIHBvciBpbnNlcmlyIGVzdGEgaW5mb3JtYcOnw6NvIG5hcyB2aXN1YWxpemHDp8O1ZXMuDQoNCkFzIGNvcmVzIHNlbGVjaW9uYWRhcyBmb3JhbSBlc2NvbGhpZGFzIGEgcGFydGlyIGRhIHZlc3RpbWVudGEgZG9zIHBlcnNvbmFnZW5zIGUgZGEgc2VndWludGUgaW1hZ2VtOg0KDQoNCiFbPGEgaHJlZj0iaHR0cHM6Ly9ici5waW50ZXJlc3QuY29tL3Bpbi8yMjg3Njg4NTYwNDI1MDE2MTEvIj5Gb250ZSBkYSBpbWFnZW08L2E+XShkYXRhL3BpbnRlcmVzdC5wbmcpDQpgYGB7cn0NCndhdGVyID0gIiMyN0EzRUIiDQpmaXJlID0gIiNENzMzM0MiDQplYXJ0aCA9ICIjNkZDQzM2Ig0KYWlyID0gImdvbGQxIg0KYGBgDQoNCmBgYHtyfQ0KcGVyc29uYWdlbnMgPSBjb3VudF9zcGVha2VyJHNwZWFrZXJbMToxOF0NCm9yaWdpbiA9IHRpYmJsZShzcGVha2VyID0gcGVyc29uYWdlbnMsIG9yaWdpbiA9IGMoIndhdGVyIiwgImFpciIsICJ3YXRlciIsICJmaXJlIiwgImZpcmUiLCAiZWFydGgiLCAiZmlyZSIsICJmaXJlIiwgImVhcnRoIiwgIndhdGVyIiwgImFpciIsICJmaXJlIiwgImZpcmUiLCAiZWFydGgiLCAid2F0ZXIiLCAiZWFydGgiLCAiZWFydGgiLCAiZmlyZSIpKQ0Kb3JpZ2luDQpgYGANCg0KYGBge3IsIGZpZy53aWR0aCA9IDEwLCBkcGkgPSAzMDAsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQ0KY291bnRfc3BlYWtlciRTZWxlY2lvbmFkbyA9ICJOw6NvIg0KY291bnRfc3BlYWtlciRTZWxlY2lvbmFkb1sxOjE4XSA9ICJTaW0iDQpuX291dHJvcyA9IHN1bShjb3VudF9zcGVha2VyJG5bd2hpY2goY291bnRfc3BlYWtlciRTZWxlY2lvbmFkbyA9PSAiTsOjbyIpXSkNCmNvdW50X3NwZWFrZXIgJT4lDQogIGhlYWQoMjQpICU+JQ0KICByYmluZCh0aWJibGUoc3BlYWtlciA9ICJPdXRyb3MiLCBuID0gbl9vdXRyb3MsIFNlbGVjaW9uYWRvID0gIk7Do28iKSkgJT4lDQogIG11dGF0ZShzcGVha2VyID0gZmFjdG9yKHNwZWFrZXIsIGxldmVscyA9IHNwZWFrZXIpKSAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gc3BlYWtlciwgeSA9IG4sIGFscGhhID0gU2VsZWNpb25hZG8sIGZpbGwgPSBzcGVha2VyKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKw0KICB4bGFiKCJQZXJzb25hZ2VtIikgKw0KICB5bGFiKCJRdWFudGlkYWRlIGRlIHBhbGF2cmFzIikgKw0KICB0aGVtZV9idygpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgdmp1c3QgPSAwLjMpKSArDQogIHNjYWxlX2FscGhhX2Rpc2NyZXRlKHJhbmdlID0gYygwLjMsIDAuOSkpICsNCiAgZ2d0aXRsZSgiUGFsYXZyYXMgcG9yIHBlcnNvbmFnZW5zIGUgcGVyc29uYWdlbnMgc2VsZWNpb25hZG9zIikgKw0KICBndWlkZXMoYWxwaGEgPSBndWlkZV9sZWdlbmQocmV2ZXJzZSA9IFRSVUUpLCBmaWxsID0gRkFMU0UpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYyh3YXRlciwgYWlyLCB3YXRlciwgZmlyZSwgZmlyZSwgZWFydGgsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlyZSwgZmlyZSwgZWFydGgsIHdhdGVyLCBhaXIsIGZpcmUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlyZSwgZWFydGgsIHdhdGVyLCBlYXJ0aCwgZWFydGgsIGZpcmUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZWFydGgsIHdhdGVyLCBmaXJlLCBmaXJlLCBmaXJlLCB3YXRlciwgImJsYWNrIikpDQpgYGANCg0KT3B0ZWkgcG9yIHV0aWxpemFyIGFwZW5hcyBvcyAxOCBwZXJzb25hZ2VucyBjb20gbWFpb3IgbsO6bWVybyBkZSBwYWxhdnJhcyBlIGNhbGN1bGFyIGFzIHBhbGF2cmFzIG1haXMgdXRpbGl6YWRhcyBlIGEgbWVkaWRhIHRmLWlkZiBjb25zaWRlcmFuZG8gY2FkYSBwZXJzb25hZ2VtIGNvbW8gdW0gZG9jdW1lbnRvLiBBIG1lZGlkYSB0Zi1pZGYgbm9zIHBlcm1pdGUgYXZhbGlhciBwYWxhdnJhcyBxdWUgY2FyYWN0ZXJpemFtIHVtIGRldGVybWluYWRvIGRvY3VtZW50bywgbmVzdGUgY2FzbyBwZXJzb25hZ2VtLCBlbSByZWxhw6fDo28gYSB1bSBjb25qdW50byBkZSBkb2N1bWVudG9zLiBFbGEgw6kgY2FsY3VsYWRhIGRhIHNlZ3VpbnRlIGZvcm1hOg0KDQokJCB3X3t0LGR9ID0gXGZyYWN7dGZfe3QsZH19e1xzdW1fe3QnIFxpbiBkfWZfe3QnLGR9fSBcdGltZXMgbG9nIFxCaWdnKCBcZnJhY3tOfXtuX3R9IFxCaWdnKSAkJA0KDQplbSBxdWUNCg0KKyAkd197dCxkfSQgw6kgbyB2YWxvciBkbyBvIHBlc28gXHRleHRpdHt0Zi1pZGZ9IHBhcmEgbyB0ZXJtbyAkdCQgbm8gZG9jdW1lbnRvICRkJDsNCg0KKyAkdGZfe3QsZH0kIMOpIGEgcXVhbnRpZGFkZSBkZSB2ZXplcyBxdWUgbyB0ZXJtbyAkdCQgYXBhcmVjZSBubyBkb2N1bWVudG8gJGQkOw0KDQorICRcc3VtX3t0JyBcaW4gZH1mX3t0JyxkfSQgw6kgYSBxdWFudGlkYWRlIHRvdGFsIGRlIHRlcm1vcyBkbyBkb2N1bWVudG8gJGQkOw0KDQorICROJCDDqSBvIG7Dum1lcm8gdG90YWwgZGUgZG9jdW1lbnRvczsNCg0KKyAkbl90JCDDqSBvIG7Dum1lcm8gZGUgZG9jdW1lbnRvcyBxdWUgY29udMOpbSBvIHRlcm1vICR0JC4NCg0KYGBge3J9DQp0ZmlkZiA9IHV0ICU+JQ0KICBmaWx0ZXIoc3BlYWtlciAlaW4lIHBlcnNvbmFnZW5zKSAlPiUNCiAgY291bnQod29yZCwgc3BlYWtlcikgJT4lDQogIGJpbmRfdGZfaWRmKHdvcmQsIHNwZWFrZXIsIG4pICU+JQ0KICBhcnJhbmdlKGRlc2ModGZfaWRmKSkNCmBgYA0KDQpWaXN1YWxpemFuZG8gYXMgcGFsYXZyYXMgbWFpcyB1dGlsaXphZGFzIGUgb3MgbWFpb3JlcyB0Zi1pZGYgcG9yIHBlcnNvbmFnZW0NCmBgYHtyfQ0KdG1wMSA9IHRpYmJsZShzcGVha2VyID0gY291bnRfc3BlYWtlciRzcGVha2VyWzE6MThdLCANCiAgICAgICAgICAgICAgICAgICBvcmRlcl9zcGVha2VyID0gMToxOCkNCg0KdG1wMiA9IHRmaWRmICU+JQ0KICBpbm5lcl9qb2luKHRtcDEsIGJ5ID0gInNwZWFrZXIiKSAlPiUNCiAgbXV0YXRlKHNwZWFrZXIgPSByZW9yZGVyKHNwZWFrZXIsIG9yZGVyX3NwZWFrZXIpKSAlPiUNCiAgYXJyYW5nZShzcGVha2VyLCBkZXNjKG4pKSAlPiUNCiAgZ3JvdXBfYnkoc3BlYWtlcikgJT4lDQogIGRvKGhlYWQoLiwgMTApKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBhcnJhbmdlKHNwZWFrZXIsIG4pICU+JQ0KICBtdXRhdGUob3JkZXJfd29yZCA9IHJvd19udW1iZXIoKSkNCg0KdG1wMyA9IHRmaWRmICU+JQ0KICBpbm5lcl9qb2luKHRtcDEsIGJ5ID0gInNwZWFrZXIiKSAlPiUNCiAgbXV0YXRlKHNwZWFrZXIgPSByZW9yZGVyKHNwZWFrZXIsIG9yZGVyX3NwZWFrZXIpKSAlPiUNCiAgYXJyYW5nZShzcGVha2VyLCBkZXNjKHRmX2lkZikpICU+JQ0KICBncm91cF9ieShzcGVha2VyKSAlPiUNCiAgZG8oaGVhZCguLCAxMCkpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIGFycmFuZ2Uoc3BlYWtlciwgdGZfaWRmKSAlPiUNCiAgbXV0YXRlKG9yZGVyX3dvcmQgPSByb3dfbnVtYmVyKCkpDQpgYGANCg0KYGBge3IsIGZpZy53aWR0aCA9IDEwLCBmaWcuaGVpZ2h0ID0gMTAsIGRwaSA9IDMwMH0NCnRtcDIgJT4lDQogIGdncGxvdChhZXMoeCA9IG9yZGVyX3dvcmQsIHkgPSBuLCBmaWxsID0gc3BlYWtlcikpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHNob3cubGVnZW5kID0gRkFMU0UpICsNCiAgZmFjZXRfd3JhcCh+IHNwZWFrZXIsIHNjYWxlcyA9ICJmcmVlIiwgbmNvbCA9IDYpICsNCiAgdGhlbWVfYncoKSArDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSB0bXAyJG9yZGVyX3dvcmQsIGxhYmVscyA9IHRtcDIkd29yZCkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoYnJlYWtzID0gc2NhbGVzOjpwcmV0dHlfYnJlYWtzKDIpKSArDQogIGNvb3JkX2ZsaXAoKSArDQogIGxhYnModGl0bGUgPSAiUGFsYXZyYXMgbWFpcyB1dGlsaXphZGFzIHBvciBwZXJzb25hZ2VucyIsDQogICAgICAgeCA9IE5VTEwsDQogICAgICAgeSA9ICJuIikgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKHdhdGVyLCBhaXIsIHdhdGVyLCBmaXJlLCBmaXJlLCBlYXJ0aCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaXJlLCBmaXJlLCBlYXJ0aCwgd2F0ZXIsIGFpciwgZmlyZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaXJlLCBlYXJ0aCwgd2F0ZXIsIGVhcnRoLCBlYXJ0aCwgZmlyZSkpDQpgYGANCg0KYGBge3IsIGZpZy53aWR0aCA9IDEwLCBmaWcuaGVpZ2h0ID0gMTAsIGRwaSA9IDMwMH0NCnRtcDMgJT4lDQogIGdncGxvdChhZXMoeCA9IG9yZGVyX3dvcmQsIHkgPSB0Zl9pZGYsIGZpbGwgPSBzcGVha2VyKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5Iiwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKw0KICBmYWNldF93cmFwKH4gc3BlYWtlciwgc2NhbGVzID0gImZyZWUiLCBuY29sID0gNikgKw0KICB4bGFiKCJ3b3JkcyIpICsNCiAgeWxhYigidGYtaWRmIikgKw0KICB0aGVtZV9idygpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHRtcDMkb3JkZXJfd29yZCwgbGFiZWxzID0gdG1wMyR3b3JkKSArDQogIHNjYWxlX3lfY29udGludW91cyhicmVha3MgPSBzY2FsZXM6OnByZXR0eV9icmVha3MoMikpICsNCiAgY29vcmRfZmxpcCgpICsNCiAgbGFicyh0aXRsZSA9ICJQYWxhdnJhcyBjb20gbWFpb3JlcyB0Zi1pZGYgcG9yIHBlcnNvbmFnZW0iLA0KICAgICAgIHggPSBOVUxMLA0KICAgICAgIHkgPSAidGYtaWRmIikgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKHdhdGVyLCBhaXIsIHdhdGVyLCBmaXJlLCBmaXJlLCBlYXJ0aCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaXJlLCBmaXJlLCBlYXJ0aCwgd2F0ZXIsIGFpciwgZmlyZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaXJlLCBlYXJ0aCwgd2F0ZXIsIGVhcnRoLCBlYXJ0aCwgZmlyZSkpDQpgYGANCg0KRmluYWxtZW50ZSwgZml6IHVtYSB2aXN1YWxpemHDp8OjbyBkYSBtYXRyaXogZGUgZnJlcXXDqm5jaWEgZG9zIHRlcm1vcyBkZXN0ZXMgcGVyc29uYWdlbnMgdXRpbGl6YW5kbyBhbsOhbGlzZSBkZSBhZ3J1cGFtZW50b3MgaGllcsOhcnF1aWNhIHBhcmEgZmF6ZXIgYSBvcmRlbmHDp8OjbyBkYXMgbGluaGFzIGUgY29sdW5hcyBkZXN0YSBtYXRyaXouDQpgYGB7ciwgZmlnLndpZHRoID0gMTAsIGZpZy5oZWlnaHQgPSAxMCwgZHBpID0gMzAwfQ0KbWF0ID0gcmVzaGFwZTI6OmFjYXN0KHRmaWRmLCB3b3JkIH4gc3BlYWtlciwgdmFsdWUudmFyID0gInRmIiwgZmlsbCA9IDApIA0Kcm93bmFtZXMobWF0KSA9IHJlcCgiIiwgbnJvdyhtYXQpKSANCmhlYXRtYXAobWF0KQ0KYGBgDQoNCk8gcXVlIGNoYW1hIGEgYXRlbsOnw6NvIG5lc3RhIG1hdHJpeiDDqSBxdWUgZXhpc3RlbSBkaXZlcnNvcyB0ZXJtb3MgcXVlIHPDo28gdXRpbGl6YWRvcyBhcGVuYXMgcG9yIHVtIMO6bmljbyBwZXJzb25hZ2VtLg0KDQpFeGVtcGxvIGRlIHRlcm1vcyBxdWUgYXBlbmFzIG8gcGVyc29uYWdlbSAiU29ra2EiIGRpc3NlOg0KDQpgYGB7cn0NCnNva2thID0gdXQgJT4lDQogIGZpbHRlcihzcGVha2VyID09ICJTb2trYSIpICU+JQ0KICBjb3VudCh3b3JkKQ0KDQpub3Rfc29ra2EgPSB1dCAlPiUNCiAgZmlsdGVyKHNwZWFrZXIgIT0gIlNva2thIikgJT4lDQogIHNlbGVjdCh3b3JkKSAlPiUNCiAgZGlzdGluY3QoLmtlZXBfYWxsID0gVFJVRSkNCg0Kc29ra2EgJT4lDQogIGFudGlfam9pbihub3Rfc29ra2EsIGJ5ID0gIndvcmQiKSAlPiUNCiAgYXJyYW5nZShkZXNjKG4pKQ0KYGBgDQoNCiMjIFJlbGHDp8OjbyBlbnRyZSBhIHF1YW50aWRhZGUgZGUgcGFsYXZyYXMgZG9zIHBlcnNvbmFnZW5zIGUgbm90YXMgZG8gSU1EQg0KDQpQcmltZWlyYW1lbnRlLCBhZ3J1cGVpIGFzIGluZm9ybWHDp8O1ZXMgZGFzIG5vdGFzIGNvbSBhIGJhc2UgZG8gcm90ZWlybyBlIGNyaWVpIHVtYSB2YXJpw6F2ZWwgcGFyYSBpbmRpY2FyIGEgcXVhbnRpZGFkZSBkZSBwYWxhdnJhcyBkb3MgcGVyc29uYWdlbnMgcG9yIGVwaXPDs2Rpby4NCmBgYHtyfQ0KdG1wID0gdGliYmxlKGVwaV9udW0gPSB1bmlxdWUodHJhbnNjcmlwdHMkZXBpX251bSksIHJhdGluZyA9IHJhdGluZ3MkcmF0aW5nKQ0KDQp3b3Jkc19lcGkgPSB1dCAlPiUNCiAgaW5uZXJfam9pbih0bXAsIGJ5ID0gImVwaV9udW0iKSAlPiUNCiAgZmlsdGVyKHNwZWFrZXIgJWluJSBwZXJzb25hZ2VucykgJT4lDQogIGNvdW50KGVwaV9udW0sIHJhdGluZywgc3BlYWtlcikNCg0Kd29yZHNfZXBpDQpgYGANClBvciBleGVtcGxvIG8gcGVyc29uYWdlbSBBYW5nIGRpc3NlIDEyNSBwYWxhdnJhcyAoY29tIHJlbW/Dp8OjbyBkZSBzdG9wIHdvcmRzKSBubyBlcGlzw7NkaW8gMS4NCg0KQWdvcmEsIHZpc3VhbGl6YW1vcyBhIGJhc2UgY29tIGFzIG5vdGFzIGRvcyBlcGlzw7NkaW9zIGUgYSBxdWFudGlkYWRlIGRlIGZhbGFzIGRvcyBwZXJzb25hZ2VucyBwb3IgZXBpc8OzZGlvLg0KYGBge3IsIGRwaSA9IDMwMCwgZmlnLndpZHRoID0gMn0NCnJhdGluZ3MkVGVtcG9yYWRhID0gYXMuZmFjdG9yKGMocmVwKCJCb29rIE9uZTogQWlyIiwgMjApLCByZXAoIkJvb2sgdHdvOiBFYXJ0aCIsIDIwKSwgcmVwKCJCb29rIFRocmVlOiBGaXJlIiwgMjEpKSkNCg0KcCA9IHJhdGluZ3MgJT4lDQogIG11dGF0ZSh0ZXh0ID0gcGFzdGUwKCJFcGlzw7NkaW86ICIsIGVwaV9udW0sICI8YnI+IiwNCiAgICAgICAgICAgICAgICAgICAgICAgIlTDrXR1bG8gIiwgZXBpX25hbWUsICI8YnI+IiwNCiAgICAgICAgICAgICAgICAgICAgICAgIk5vdGE6ICIsIHJhdGluZywgIjxicj4iKSkgJT4lDQogIGdncGxvdChhZXMoeCA9IGVwaV9udW0sIHkgPSByYXRpbmcsIGZpbGwgPSBUZW1wb3JhZGEsIHRleHQgPSB0ZXh0KSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKHdhdGVyLCBlYXJ0aCwgZmlyZSkpICsNCiAgdGhlbWVfYncoKSArDQogIGNvb3JkX2NhcnRlc2lhbih5bGltID0gYygwLCAxMCkpICsNCiAgZ2d0aXRsZSgiTm90YSBkbyBJTURCIHBvciBlcGlzw7NkaW8iKSArDQogIHhsYWIoIkVwaXPDs2RpbyIpICsNCiAgeWxhYigiTm90YSIpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCkpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCkpDQogIA0KcCAlPiUgZ2dwbG90bHkodG9vbHRpcCA9IGMoInRleHQiKSkNCmBgYA0KDQpgYGB7ciwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9DQp0bXA0ID0gcmVzaGFwZTI6OmFjYXN0KHdvcmRzX2VwaSwgZXBpX251bSB+IHNwZWFrZXIsIHZhbHVlLnZhciA9ICJuIiwgZmlsbCA9IDApICU+JQ0KICByZXNoYXBlMjo6bWVsdCgpDQpuYW1lcyh0bXA0KSA9IGMoImVwaV9udW0iLCAic3BlYWtlciIsICJuIikNCnRtcDQgPSB0bXA0ICU+JQ0KICBpbm5lcl9qb2luKHRtcDEsIGJ5ID0gInNwZWFrZXIiKSAlPiUNCiAgbXV0YXRlKHNwZWFrZXIgPSByZW9yZGVyKHNwZWFrZXIsIG9yZGVyX3NwZWFrZXIpKSANCmBgYA0KDQpgYGB7ciwgZmlnLndpZHRoID0gMTYsIGZpZy5oZWlnaHQgPSAxMCwgZHBpID0gMzAwfQ0KdG1wNCAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gZXBpX251bSwgeSA9IG4sIGZpbGwgPSBzcGVha2VyKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5Iiwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKw0KICBmYWNldF93cmFwKH4gc3BlYWtlciwgc2NhbGVzID0gImZyZWUiLCBuY29sID0gMykgKw0KICB5bGltKDAsIDMwMCkgKw0KICB0aGVtZV9idygpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYyh3YXRlciwgYWlyLCB3YXRlciwgZmlyZSwgZmlyZSwgZWFydGgsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlyZSwgZmlyZSwgZWFydGgsIHdhdGVyLCBhaXIsIGZpcmUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlyZSwgZWFydGgsIHdhdGVyLCBlYXJ0aCwgZWFydGgsIGZpcmUpKSArDQogIGdndGl0bGUoIlF1YW50aWRhZGUgZGUgcGFsYXZyYXMgcG9yIGVwaXPDs2RpbyIpICsNCiAgeGxhYigiRXBpc8OzZGlvIikgKw0KICB5bGFiKCJRdWFudGlkYWRlIGRlIHBhbGF2cmFzIikgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSkNCmBgYA0KDQpQb3N0ZXJpb3JtZW50ZSwgY2FsY3VsZWkgYSBjb3JyZWxhw6fDo28gZGUgU3BlYXJtYW4gZW50cmUgYSBwcm9wb3LDp8OjbyBkZSBmYWxhcyBkbyBwZXJzb25hZ2VtIGUgYSBub3RhIGRvIElNREIuIFByZWZlcmkgdXRpbGl6YXIgZXN0YSBtZWRpZGEgZGUgY29ycmVsYcOnw6NvIGFvIGludsOpcyBkYSBjb3JyZWxhw6fDo28gZGUgUGVhcnNvbiBwb2lzIG7Do28gZXNwZXJvIHF1ZSBhIGNvcnJlbGHDp8OjbyBlbnRyZSBlc3RhcyB2YXJpw6F2ZWlzIHNlamEgbGluZWFyIHZpc3RvIHF1ZSBleGlzdGVtIG11aXRvcyBwZXJzb25hZ2VucyBxdWUgbsOjbyBwb3NzdWVtIGZhbGFzIGVtIGRpdmVyc29zIGVwaXPDs2Rpb3MuDQpgYGB7cn0NCmYgPC0gZnVuY3Rpb24ocGVyc29uYWdlbSkgew0KICBvdXQgPSBOVUxMDQogIGZvcihpIGluIDE6NjEpIHsNCiAgICB0bXAgPSB3b3Jkc19lcGkgJT4lDQogICAgICBmaWx0ZXIoc3BlYWtlciA9PSBwZXJzb25hZ2VtLA0KICAgICAgICAgICAgIGVwaV9udW0gPT0gaSkgJT4lDQogICAgICAuJG4NCiAgICBvdXRbaV0gPSBpZmVsc2UobGVuZ3RoKHRtcCkgPT0gMCwgMCwgdG1wKQ0KICB9DQogIG91dA0KfQ0KDQptYXQyID0gcmF0aW5ncyRyYXRpbmcgJT4lDQogIGNiaW5kKHNhcHBseShwZXJzb25hZ2VucywgZikpDQpjb2xuYW1lcyhtYXQyKVsxXSA9ICJyYXRpbmciDQoNCm1hdF9yYXRpbmcgPSBjb3IobWF0MiwgbWV0aG9kID0gInNwZWFybWFuIikNCg0KY29yX3JhdGluZyA9IG1hdF9yYXRpbmdbLTEsIDFdDQoNCnNvcnQoY29yX3JhdGluZywgZGVjcmVhc2luZyA9IFRSVUUpDQpgYGANCg0KYGBge3IsIGZpZy53aWR0aCA9IDEwLCBkcGkgPSAzMDB9DQp0aWJibGUoc3BlYXJtYW4gPSBjb3JfcmF0aW5nLCBzcGVha2VyID0gbmFtZXMoY29yX3JhdGluZykpICU+JQ0KICBpbm5lcl9qb2luKG9yaWdpbiwgYnkgPSAic3BlYWtlciIpICU+JQ0KICBtdXRhdGUoc3BlYWtlciA9IHJlb3JkZXIoc3BlYWtlciwgc3BlYXJtYW4pKSAlPiUNCiAgZ2dwbG90KGFlcyhzcGVha2VyLCBzcGVhcm1hbiwgZmlsbCA9IG9yaWdpbikpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsNCiAgeGxhYigiUGVyc29uYWdlbSIpICsNCiAgeWxhYigiQ29ycmVsYcOnw6NvIGRlIFNwZWFybWFuIikgKw0KICBnZ3RpdGxlKCJDb3JyZWxhw6fDtWVzIGRlIFNwZWFybWFuIFxuZW50cmUgYSBxdWFudGlkYWRlIGRlIHBhbGF2cmFzIGRvIHBlcnNvbmFnZW0gcG9yIGVwaXPDs2RpbyBlIG5vdGFzIGRvIElNREIiKSArDQogIHRoZW1lX2J3KCkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoYnJlYWtzID0gc2NhbGVzOjpwcmV0dHlfYnJlYWtzKDMpKSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoIndhdGVyIiA9IHdhdGVyLCAiZmlyZSIgPSBmaXJlLCAiYWlyIiA9IGFpciwgImVhcnRoIiA9IGVhcnRoKSkgKw0KICBndWlkZXMoZmlsbCA9IEZBTFNFKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHZqdXN0ID0gMC4zKSkNCmBgYA0KDQoNCiMjIFJlbGHDp8O1ZXMgZW50cmUgb3MgcGVyc29uYWdlbnMNCg0KVXRpbGl6YW5kbyBhIG1hdHJpeiBkZSBjb3JyZWxhw6fDo28gY3JpYWRhIG5hIHNlw6fDo28gYW50ZXJpb3IsIHJlYWxpemVpIHVtYSBhbsOhbGlzZSBkZSBhZ3J1cGFtZW50b3MgaGllcsOhcnF1aWNhIHBhcmEgYnVzY2FyIHBlcnNvbmFnZW5zIHF1ZSB0ZW5kZW0gYSB0ZXIgcXVhbnRpZGFkZXMgZGUgZmFsYXMgc2ltaWxhcmVzIG5vcyBtZXNtb3MgZXBpc8OzZGlvcy4NCmBgYHtyLCBkcGkgPSAzMDB9DQpjbHVzdGVycyA9IGhjbHVzdChhcy5kaXN0KDEtbWF0X3JhdGluZ1stMSwtMV0pKQ0KZ2dkZW5kcm86OmdnZGVuZHJvZ3JhbShjbHVzdGVycykNCmBgYA0KDQpQb2RlbW9zIHZlciBxdWUgYXMgbWFpb3JlcyBzaW1pbGFyaWRhZGVzIGVzdMOjbyBlbnRyZSBBenVsYS9NYWkgZSBTb2trYS9LYXRhcmEuIE8gcXVlIGZheiBzZW50aWRvIHZpc3RvIHF1ZSBlc3RlcyBwYXJlcyB0ZW5kZW0gYSBhcGFyZWNlciBqdW50b3Mgbm9zIGVwaXPDs2Rpb3MuDQoNClBvc3Rlcmlvcm1lbnRlLCBjb25zaWRlcmVpIGFwZW5hcyBhcyBwYWxhdnJhcyByZWZlcmVudGVzIGFvcyBub21lcyBkb3MgMTggcGVyc29uYWdlbnMgc2VsZWNpb25hZG9zIGUgaWx1c3RyZWkgYSBxdWFudGlkYWRlIGRlIHZlemVzIHF1ZSB1bSBwZXJzb25hZ2VtIGRpeiBvIG5vbWUgZGUgb3V0cm8uIE5vdGUgcXVlIGEgZGlmZXJlbsOnYSBkYXMgYWx0dXJhcyBpbmRpY2FtIGEgZGlyZcOnw6NvIGRhcyBmYWxhcy4NCmBgYHtyfQ0KdG1wNiA9IHRmaWRmICU+JQ0KICBmaWx0ZXIod29yZCAlaW4lIHN0cmluZ3I6OnN0cl90b19sb3dlcihjKHBlcnNvbmFnZW5zLCAiZmVuZyIpKSkgJT4lDQogIG11dGF0ZShzcGVha2VyID0gc3RyaW5ncjo6c3RyX3RvX2xvd2VyKHNwZWFrZXIpKQ0KdG1wNiRzcGVha2VyW3doaWNoKHRtcDYkc3BlYWtlciA9PSAibG9uZyBmZW5nIildID0gImZlbmciDQoNCm1hdDMgPSByZXNoYXBlMjo6YWNhc3QodG1wNiwgc3BlYWtlciB+IHdvcmQsIHZhbHVlLnZhciA9ICJuIiwgZmlsbCA9IDApIA0KDQpjb2xuYW1lcyhtYXQzKSA9IGMoIkFhbmciLCAiQXp1bGEiLCAiQnVtaSIsICJGZW5nIiwgIkhha29kYSIsICJIYW1hIiwgIklyb2giLA0KICAgICAgICAgICAgICAgICAgIkpldCIsICJLYXRhcmEiLCAiTWFpIiwgIk96YWkiLCAiUGF0aGlrIiwgIlJva3UiLCAiU29ra2EiLA0KICAgICAgICAgICAgICAgICAgIlN1a2kiLCAiVG9waCIsICJaaGFvIiwgIlp1a28iKQ0Kcm93bmFtZXMobWF0MykgPSBjb2xuYW1lcyhtYXQzKQ0KYGBgDQoNCmBgYHtyLCBmaWcud2lkdGggPSAyMCwgZmlnLmhlaWdodCA9IDIwLCBkcGkgPSAzMDB9DQpncmlkLmNvbCA9IGMoQWFuZyA9IGFpciwgQXp1bGEgPSBmaXJlLCBCdW1pID0gZWFydGgsIEZlbmcgPSBlYXJ0aCwgSGFrb2RhID0gd2F0ZXIsDQogICAgICAgICAgICAgSGFtYSA9IHdhdGVyLCBJcm9oID0gZmlyZSwgSmV0ID0gZWFydGgsIEthdGFyYSA9IHdhdGVyLCBNYWkgPSBmaXJlLA0KICAgICAgICAgICAgIE96YWkgPSBmaXJlLCBQYXRoaWsgPSBhaXIsIFJva3UgPSBmaXJlLCBTb2trYSA9IHdhdGVyLCBTdWtpID0gZWFydGgsDQogICAgICAgICAgICAgVG9waCA9IGVhcnRoLCBaaGFvID0gZmlyZSwgWnVrbyA9IGZpcmUpDQoNCmNpcmNvcy5jbGVhcigpDQpjaXJjb3MucGFyKHN0YXJ0LmRlZ3JlZSA9IDkwLCBjbG9jay53aXNlID0gVFJVRSkNCmNob3JkRGlhZ3JhbShtYXQzLCBncmlkLmNvbCA9IGdyaWQuY29sLCBhbm5vdGF0aW9uVHJhY2sgPSBjKCJuYW1lIiwgImdyaWQiKSwgZGlyZWN0aW9uYWwgPSAxLCBkaWZmSGVpZ2h0ID0gdWgoNyksIG9yZGVyID0gYygiUGF0aGlrIiwgIkFhbmciLCAiS2F0YXJhIiwgIlNva2thIiwgIkhha29kYSIsICJIYW1hIiwgIlRvcGgiLCAiQnVtaSIsICJGZW5nIiwgIkpldCIsICJTdWtpIiwgIlp1a28iLCAiUm9rdSIsICJPemFpIiwgIklyb2giLCAiQXp1bGEiLCAiTWFpIiwgIlpoYW8iKSkNCmBgYA0KDQojIyBSZWZlcsOqbmNpYXMNCg0KDQoNCg0KDQoNCg0KDQoNCg0K